//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
namespace LargoCommon.Midi
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using JetBrains.Annotations;
using Music;
///
/// Midi Blocks Solver.
///
public sealed class MidiBlocksSolver
{
#region Fields
///
/// Tempo Serious Change Tolerance.
///
private const int TempoSeriousChangeTolerance = 25; //// 20- 30
//// private const int tempoChangeTolerance = 10; //// 10
///
/// Tempo Block Change Tolerance.
///
private const int TempoBlockChangeLimit = 60;
///
/// Minimum Block Length.
///
private const int MinimumBlockLength = 20;
///
/// Break events.
///
private readonly IEnumerable breakEvents;
///
/// Block Record.
///
private BlockRecord currentRecord;
///
/// Block Record.
///
private BlockRecord lastRecord;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The given sequence.
/// The given break events.
public MidiBlocksSolver(CompactMidiStrip givenSequence, IEnumerable givenBreakEvents)
{
this.Sequence = givenSequence;
this.breakEvents = givenBreakEvents;
}
///
/// Initializes a new instance of the class.
///
[UsedImplicitly]
public MidiBlocksSolver()
{
}
#endregion
#region Properties
///
/// Gets or sets the header.
///
///
/// The header.
///
public CompactMidiStrip Sequence { get; set; }
///
/// Gets the main events.
///
///
/// Property description.
///
private IEnumerable MainEvents
{
get
{
var selectedEvents = new List();
MidiEvent lastEvent = null;
var lastTempo = 0;
foreach (var ev in this.breakEvents.Where(ev => ev != null))
{
this.ReadCurrentValues(ev);
if (string.CompareOrdinal(this.currentRecord.EventType, "MetaTempo") == 0)
{
if (lastEvent == null)
{
selectedEvents.Add(ev);
lastEvent = ev;
lastTempo = this.currentRecord.Tempo;
continue;
}
var tempoChange = Math.Abs(lastTempo - this.currentRecord.Tempo);
if (tempoChange <= 0)
{
continue;
}
if (tempoChange <= TempoSeriousChangeTolerance)
{
continue;
}
selectedEvents.Add(ev);
lastTempo = this.currentRecord.Tempo;
}
else
{
selectedEvents.Add(ev);
}
}
return selectedEvents;
}
}
#endregion
///
/// Determines the blocks.
///
/// The final event.
///
/// Returns value.
///
public IEnumerable DetermineBlocks(IMidiEvent finalEvent)
{
var mainEvents = this.MainEvents;
var midiBlocks = new Collection();
this.currentRecord = new BlockRecord(string.Empty, 4, 2, TonalityKey.None, 0);
this.lastRecord = new BlockRecord(string.Empty, 4, 2, TonalityKey.None, 0);
var lastBarNumber = 1;
long lastStartTime = 0;
MidiBlock midiBlock = null;
int barNumber, barDifference;
foreach (var ev in mainEvents.Where(ev => ev != null))
{
this.ReadCurrentValues(ev);
barNumber = this.DetermineBarNumber(this.Sequence.Header.Division, ev);
barDifference = barNumber - lastBarNumber;
var streamChange = this.DetermineTypeOfChange(this.lastRecord.Tempo, this.lastRecord.MetricBeat, this.lastRecord.MetricBase); //// this.lastRecord.TonalityKey
if (streamChange != MidiStreamChange.Serious)
{ //// || (streamChange == MidiStreamChange.Common && (barDifference > 31)
continue;
}
if (barDifference > 0)
{
//// Check tempo in the previous block
midiBlock?.CheckTempo();
if (this.Sequence.Header.Clone() is MusicalHeader header)
{
header.Metric.MetricBase = this.lastRecord.MetricBase;
header.Metric.MetricBeat = this.lastRecord.MetricBeat;
midiBlock = new MidiBlock(
lastBarNumber,
header,
this.lastRecord.TonalityKey,
this.lastRecord.Tempo)
{
MidiTimeFrom = lastStartTime,
MidiTimeTo = ev.StartTime,
Area = { BarTo = barNumber - 1 }
};
}
midiBlocks.Add(midiBlock);
if (midiBlock?.Header != null)
{
midiBlock.Header.Number = midiBlocks.Count;
}
lastBarNumber = barNumber; //// 2013/03
lastStartTime = ev.StartTime;
}
//// Properties of the last block
//// lastBarNumber = barNumber; 2013/03
this.lastRecord.GetValuesFrom(this.currentRecord);
}
//// Check tempo in the previous block
midiBlock?.CheckTempo();
// ReSharper disable once InvertIf
if (finalEvent != null)
{
this.ReadCurrentValues(finalEvent);
barNumber = this.DetermineBarNumber(this.Sequence.Header.Division, finalEvent);
barDifference = barNumber - lastBarNumber;
// ReSharper disable once InvertIf
if (barDifference > 0)
{
if (this.Sequence.Header.Clone() is MusicalHeader musicHeader)
{
musicHeader.Metric.MetricBase = this.lastRecord.MetricBase;
musicHeader.Metric.MetricBeat = this.lastRecord.MetricBeat;
midiBlock = new MidiBlock(
lastBarNumber,
musicHeader,
this.lastRecord.TonalityKey,
this.lastRecord.Tempo)
{
MidiTimeFrom = lastStartTime,
MidiTimeTo = finalEvent.StartTime,
Area = { BarTo = barNumber }
};
}
if (midiBlock != null)
{
midiBlock.CheckTempo();
midiBlocks.Add(midiBlock);
midiBlock.Header.Number = midiBlocks.Count;
}
}
}
var blocks = OptimizeBlocks(midiBlocks);
var determineBlocks = blocks.ToList();
foreach (var block in determineBlocks)
{
block.Sequence = new CompactMidiStrip(this.Sequence.Format, this.Sequence.Header.Division);
foreach (var originTrack in this.Sequence)
{
var track = new MidiTrack();
//// 2020/02 E.g. instruments can be defined in any previous block
var zeroEvents = (from ev in originTrack.Events
where ev.StartTime < block.MidiTimeFrom && ev.EventType == "VoiceProgramChange"
orderby ev.StartTime
select ev).ToList();
foreach (var ev in zeroEvents) { //// 2019/02
ev.StartTime = 0;
}
track.Events.AddRange(zeroEvents);
var events = (from ev in originTrack.Events
where ev.StartTime >= block.MidiTimeFrom && ev.StartTime <= block.MidiTimeTo
orderby ev.StartTime ////, tone.Duration
select ev).ToList();
foreach (var ev in events) { //// 2019/02
ev.StartTime = ev.StartTime - block.MidiTimeFrom;
}
track.Events.AddRange(events);
track.Events.RecomputeDeltaTimes(); //// 2019/02
block.Sequence.AddTrack(track);
}
}
return determineBlocks;
}
#region Private Static methods
///
/// Merges the blocks.
///
/// The midi blocks.
/// Returns value.
private static IEnumerable OptimizeBlocks(IEnumerable midiBlocks)
{
Contract.Requires(midiBlocks != null);
var blocks = midiBlocks.Where(block => block != null).ToList();
if (blocks.Count <= 1)
{
return blocks;
}
var mergedBlocks = new List();
MidiBlock lastBlock = null;
foreach (var block in blocks)
{
if (lastBlock == null)
{
mergedBlocks.Add(block);
block.Header.Number = mergedBlocks.Count;
lastBlock = block;
continue;
}
if (block.Header.Metric.MetricBase == lastBlock.Header.Metric.MetricBase && block.Header.Metric.MetricBeat == lastBlock.Header.Metric.MetricBeat
&& block.TonalityKey == lastBlock.TonalityKey
&& Math.Abs(block.Tempo - lastBlock.Tempo) < TempoBlockChangeLimit)
{
if (lastBlock.Area.Length < MinimumBlockLength || block.Area.Length < MinimumBlockLength || lastBlock.Tempo == block.Tempo)
{
lastBlock.Area.BarTo = block.Area.BarTo;
lastBlock.MidiTimeTo = block.MidiTimeTo;
continue;
}
}
mergedBlocks.Add(block);
block.Header.Number = mergedBlocks.Count;
lastBlock = block;
}
return mergedBlocks;
}
#endregion
///
/// Determines the bar number.
///
/// The given division.
/// The given event.
///
/// Returns value.
///
private int DetermineBarNumber(int givenDivision, IMidiEvent givenEvent)
{ //// out byte metricGround, out int barDivision, out int barNumber
var metricGround = MusicalProperties.GetMetricGround(this.currentRecord.MetricBase);
var barDivision = MusicalProperties.BarDivision(givenDivision, this.currentRecord.MetricBeat, metricGround);
var barNumber = (int)Math.Floor((double)givenEvent.StartTime / barDivision) + 1;
return barNumber;
}
///
/// Reads the current values.
///
/// The Midi Event.
private void ReadCurrentValues(IMidiEvent ev)
{
Contract.Requires(ev != null);
var eventType = ev.EventType;
this.currentRecord.EventType = eventType;
switch (eventType)
{
case "MetaTempo":
{
this.currentRecord.Tempo = ((MetaTempo)ev).Tempo;
break;
}
case "MetaKeySignature":
{
this.currentRecord.TonalityKey = ((MetaKeySignature)ev).Key;
break;
}
case "MetaTimeSignature":
{
var ts = (MetaTimeSignature)ev;
this.currentRecord.MetricBeat = ts.Numerator;
this.currentRecord.MetricBase = ts.Denominator;
break;
}
//// resharper default: { break; }
}
}
///
/// Determines the type of change.
///
/// The last tempo.
/// The last metric beat.
/// The last metric base.
///
/// Returns value.
///
private MidiStreamChange DetermineTypeOfChange(int lastTempo, byte lastMetricBeat, byte lastMetricBase)
{ //// TonalityKey lastTonalityKey,
//// 2014 !!?!? //// 2015/01 || (lastTonalityKey != this.currentRecord.TonalityKey)
if ((lastMetricBeat != this.currentRecord.MetricBeat)
|| (lastMetricBase != this.currentRecord.MetricBase))
{
//// || (lastTempo != this.currentRecord.Tempo)) {
return MidiStreamChange.Serious;
}
if (lastTempo == 0 && this.currentRecord.Tempo > 0)
{
return MidiStreamChange.Serious;
}
return MidiStreamChange.None;
}
}
}